﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using GeneticAlgorithm;
using PSatSimCom.RandomGenerators;

namespace PSatSimCom
{
    public class PSatSim : IGenericSimulator
    {
        #region Properties

        public List<Configuration> Input { get; set; }

        public List<SimulatedConfiguration> Output
        {
            get { return GetResult(); }
        }

        public List<string> ChangebleParameters
        {
            get { return new List<string>(Randomizes.Keys); }
        }

        public List<string> UnchangebleParameters
        {
            get { return _unchangebleParameters; }
        }

        public int TracesCount
        {
            get { return Traces.Count; }
        }

        public int ThreadCount
        {
            get { return _threadCount; }
            set { _threadCount = value; }
        }

        public string InputFileLocation
        {
            get { return string.Format("{0}\\{1}", _temporaryFilesDirectory, InputFile); }
        }

        public string OuputFileLocation
        {
            get { return string.Format("{0}\\{1}", _temporaryFilesDirectory, OuputFile); }
        }

        public static List<string> ConfigurationParameters
        {
            get { return _configurationParameters; }
        }

        #endregion

        #region Members

        private const int LevelOneCacheLatency = 1;
        private const int LevelTwoCacheLatency = 3;
        private const int MemoryLatency = 20;
        private const float VddValue = 2.2f;
        private const string Architecture = "standard";
        private const string MemArchitecture = "l2";

        private readonly string _workingDirectory;
        private readonly string _simulatorPath;
        private readonly string _temporaryFilesDirectory;
        private int _threadCount = 1;
        private static List<string> _unchangebleParameters;
        private static readonly List<string> _configurationParameters;
        private const string InputFile = "configurations_in.xml";
        private const string OuputFile = "configurations_out.xml";
        private static int _configurationIndexer = 0;

        private static readonly Dictionary<string, IRandomize> Randomizes;
        private static readonly List<string> Traces;

        #endregion

        #region Static Initializations

        static PSatSim()
        {
            Randomizes = new Dictionary<string, IRandomize>();
            Randomizes.Add("superscalar", new IntegerRandom(1, 16));
            Randomizes.Add("rename", new IntegerRandom(1, 512));
            Randomizes.Add("reorder", new IntegerRandom(1, 512));

            var rsbRand = new StringRandom(new List<string> {"distributed", "centralized", "hybrid"});
            Randomizes.Add("rsb_architecture", rsbRand);
            Randomizes.Add("rs_per_rsb", new IntegerRandom(1, 8));
            Randomizes.Add("speculative", new BooleanRandom());
            Randomizes.Add("speculation_accuracy", new DoubleRandom(0.9, 1));
            Randomizes.Add("separate_dispatch", new BooleanRandom());
            Randomizes.Add("frequency", new IntegerRandom(5, 20, 100));

            Randomizes.Add("integer", new IntegerRandom(1, 8));
            Randomizes.Add("floating", new IntegerRandom(1, 8));
            Randomizes.Add("branch", new IntegerRandom(1, 8));
            Randomizes.Add("memory", new IntegerRandom(1, 8));

            Randomizes.Add("l1Data_hitrate", new DoubleRandom(0.9, 1));
            Randomizes.Add("l1Code_hitrate", new DoubleRandom(0.9, 1));
            Randomizes.Add("l2_hitrate", new DoubleRandom(0.9, 1));

            _unchangebleParameters = new List<string>();
            _unchangebleParameters.Add("vdd");
            _unchangebleParameters.Add("architecture");
            _unchangebleParameters.Add("mem_architecture");
            _unchangebleParameters.Add("l1_latency");
            _unchangebleParameters.Add("l2_latency");
            _unchangebleParameters.Add("sys_latency");
            _unchangebleParameters.Add("seed");

            Traces = new List<string>
            {
                "applu.tra",
                "compress.tra",
                "epic.tra",
                "fpppp.tra",
                "ijpeg.tra",
                "mpeg2d.tra",
                "mpeg2e.tra",
                "pegwitd.tra",
                "perl.tra",
                "toast.tra",
            };

            _configurationParameters = new List<string>();
            _configurationParameters.Add("superscalar");
            _configurationParameters.Add("rename");
            _configurationParameters.Add("reorder");
            _configurationParameters.Add("rsb_architecture");
            _configurationParameters.Add("rs_per_rsb");
            _configurationParameters.Add("speculative");
            _configurationParameters.Add("speculation_accuracy");
            _configurationParameters.Add("separate_dispatch");
            _configurationParameters.Add("frequency");
            _configurationParameters.Add("integer");
            _configurationParameters.Add("floating");
            _configurationParameters.Add("branch");
            _configurationParameters.Add("memory");
            _configurationParameters.Add("l1Data_hitrate");
            _configurationParameters.Add("l1Code_hitrate");
            _configurationParameters.Add("l2_hitrate");
            _configurationParameters.Add("vdd");
            _configurationParameters.Add("architecture");
            _configurationParameters.Add("mem_architecture");     
            _configurationParameters.Add("l1_latency");
            _configurationParameters.Add("l2_latency");
            _configurationParameters.Add("sys_latency");
            _configurationParameters.Add("seed");
        }

        #endregion

        #region Constructors

        public PSatSim()
        {
            _workingDirectory = string.Format("{0}\\PSATSim", Directory.GetCurrentDirectory());
            _simulatorPath = string.Format("{0}\\PSATSim_con.exe", _workingDirectory);
            _temporaryFilesDirectory = string.Format("{0}\\TempFiles\\", _workingDirectory);
        }

        #endregion

        #region Public Methods

        public void Run()
        {
            Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
            PSatSimFileBuilder.CreateXml(Input, Traces, InputFileLocation);

            Console.WriteLine("Initializing simulator...");
            var arguments = string.Format("\"{0}\" \"{1}\" -g -t {2}", InputFileLocation,
                OuputFileLocation, ThreadCount);

            var process = new Process();
            process.StartInfo.FileName = _simulatorPath;
            process.StartInfo.Arguments = arguments;
            process.StartInfo.WorkingDirectory = _workingDirectory;
            process.StartInfo.UseShellExecute = false;
            //process.StartInfo.RedirectStandardOutput = true;
            process.StartInfo.CreateNoWindow = false;

            try
            {
                Console.WriteLine("Starting simulator...");
                process.Start();

                Console.WriteLine("Waiting for simulator...");
                process.WaitForExit();

                Console.WriteLine("Ending simulator...");

            }
            catch (Exception)
            {
                throw new SimulationFailedException();
            }
            
        }

        private List<SimulatedConfiguration> GetResult()
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

            var doc = XDocument.Load(OuputFileLocation);

            var nodes = doc.Root.Elements("variation");
            var result = new Dictionary<string, SimulatedConfiguration>();


            foreach (var xElement in nodes)
            {
                var name = xElement.Attribute("configuration").Value;
                name = name.Substring(name.LastIndexOf('_') + 1);
                var general = xElement.Element("general");

                if (!result.ContainsKey(name))
                {
                    result.Add(name, new SimulatedConfiguration(name));
                    result[name].FromConfiguration(Input.First(item => item.Name.Equals(name)));
                    result[name].AddScore("ipc", 0);
                    result[name].AddScore("power", 0);
                    result[name].AddScore("energy", 0);
                }

                result[name].ChangeScore("ipc", double.Parse(general.Attribute("ipc").Value) + result[name].GetScore("ipc"));
                result[name].ChangeScore("power", double.Parse(general.Attribute("power").Value) + result[name].GetScore("power"));
                result[name].ChangeScore("energy", double.Parse(general.Attribute("energy").Value) + result[name].GetScore("energy"));

            }

            foreach (var lines in result)
            {
                lines.Value.ChangeScore("ipc", lines.Value.GetScore("ipc") / TracesCount);
                lines.Value.ChangeScore("power", lines.Value.GetScore("power") / TracesCount);
                lines.Value.ChangeScore("energy", lines.Value.GetScore("energy") / TracesCount);

            }
            return new List<SimulatedConfiguration>(result.Values);
        } 

        public List<Configuration> GetInitialConfigurations(int count)
        {
            var result = new List<Configuration>();
            for (var i = 0; i < count; i++)
            {
                result.Add(CreateRandomConfiguration());
            }
            return result;
        }

        public void RandomizeConfiguration(Configuration configuration, int count)
        {
            var take = count;
            const int rangeMin = 0;
            const int rangeMax = 16;

            var randomUniqueNumbers = Enumerable.Range(0, int.MaxValue)
                .Select(i => RandomSingleton.Rand.Next(rangeMin, rangeMax))
                .Distinct()
                .Take(Math.Min(take, rangeMax - rangeMin));

            var keys = new List<string>(Randomizes.Keys);
            foreach (var randomUniqueNumber in randomUniqueNumbers)
            {
                var randomKey = keys[randomUniqueNumber];
                configuration.Change(randomKey, Randomizes[randomKey].Get());
            }
        }

        public Configuration ConfigurationFactory()
        {
            return new Configuration(string.Format("configuration-{0}", _configurationIndexer++));
        }

        public Configuration ConfigurationFactory(string name)
        {
            return new Configuration(string.Format(name));
        }

        #endregion

        #region Private Methods

        private Configuration CreateRandomConfiguration()
        {
            var config = new Configuration(string.Format("configuration-{0}", _configurationIndexer++));
            config.Add("superscalar", Randomizes["superscalar"].Get());
            config.Add("rename", Randomizes["rename"].Get());
            config.Add("reorder", Randomizes["reorder"].Get());
            config.Add("rsb_architecture", Randomizes["rsb_architecture"].Get());
            config.Add("rs_per_rsb", Randomizes["rs_per_rsb"].Get());
            config.Add("speculative", Randomizes["speculative"].Get());
            config.Add("speculation_accuracy", Randomizes["speculation_accuracy"].Get());
            config.Add("separate_dispatch", Randomizes["separate_dispatch"].Get());
            config.Add("seed", RandomSingleton.Seed.ToString("D"));
            config.Add("vdd", VddValue.ToString("F4"));
            config.Add("frequency", Randomizes["frequency"].Get());
            config.Add("architecture", Architecture);
            config.Add("integer", Randomizes["integer"].Get());
            config.Add("floating", Randomizes["floating"].Get());
            config.Add("branch", Randomizes["branch"].Get());
            config.Add("memory", Randomizes["memory"].Get());
            config.Add("mem_architecture", MemArchitecture);
            config.Add("l1Data_hitrate", Randomizes["l1Data_hitrate"].Get());
            config.Add("l1_latency", LevelOneCacheLatency.ToString("D"));
            config.Add("l1Code_hitrate", Randomizes["l1Code_hitrate"].Get());
            config.Add("l2_hitrate", Randomizes["l2_hitrate"].Get());
            config.Add("l2_latency", LevelTwoCacheLatency.ToString("D"));
            config.Add("sys_latency", MemoryLatency.ToString("D"));
            return config;
        }

        #endregion
    }
}